package com.atguigu.service.impl;

import com.atguigu.constant.RedisConst;
import com.atguigu.entity.ProductSalePropertyKey;
import com.atguigu.entity.SkuImage;
import com.atguigu.entity.SkuInfo;
import com.atguigu.exception.SleepUtils;
import com.atguigu.mapper.ProductSalePropertyKeyMapper;
import com.atguigu.service.SkuDetailService;
import com.atguigu.service.SkuImageService;
import com.atguigu.service.SkuInfoService;
import com.atguigu.service.SkuSalePropertyValueService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class SkuDetailServiceImpl implements SkuDetailService {
    @Autowired
    private SkuInfoService skuInfoService;
    @Autowired
    private SkuImageService skuImageService;
    @Autowired
    private SkuSalePropertyValueService skuSalePropertyValueService;
    @Autowired
    private ProductSalePropertyKeyMapper salePropertyKeyMapper;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RBloomFilter<Long> skuBloomFilter;
    //@ShopCache(prefix = "skuInfo",enableBloom = true)
    @Override
    public SkuInfo getSkuInfo(Long skuId) {
        SkuInfo skuInfoFromDB = getSkuInfoFromDB(skuId);
        //SkuInfo skuInfoFromRedis = getSkuInfoFromRedis(skuId);
        //SkuInfo skuInfoFromRedisThreadLocal = getSkuInfoFromRedisWithThreadLocal(skuId);
        //SkuInfo skuInfo=getSkuInfoFromRedisson(skuId);
        return skuInfoFromDB;
    }
    //利用redission实现分布式锁并拿到数据
    public SkuInfo getSkuInfoFromRedission(Long skuId){
        String cacheKey= RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        SkuInfo skuInfoRedis = (SkuInfo)redisTemplate.opsForValue().get(cacheKey);
        if(skuInfoRedis==null){
            String lockKey="lock-"+skuId;
            RLock lock = redissonClient.getLock(lockKey);
            lock.lock();
            try {
                boolean flag = skuBloomFilter.contains(skuId);
                SkuInfo skuInfoDb=null;
                if(flag){
                    skuInfoDb = getSkuInfoFromDB(skuId);
                }
                //c.把数据放入缓存
                redisTemplate.opsForValue().set(cacheKey,skuInfoDb,RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                return skuInfoDb;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        return skuInfoRedis;
    }

    //利用redission实现分布式锁并拿到数据
    public SkuInfo getSkuInfoFromRedission1(Long skuId){
        String cacheKey= RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        SkuInfo skuInfoRedis = (SkuInfo)redisTemplate.opsForValue().get(cacheKey);
        if(skuInfoRedis==null){
            String lockKey="lock-"+skuId;
            RLock lock = redissonClient.getLock(lockKey);
            lock.lock();
            try {
                SkuInfo skuInfoDb= doBusiness(skuId, cacheKey);
                return skuInfoDb;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        return skuInfoRedis;
    }



    //利用redis+lua+threadlocal查询商品基本信息
    public ThreadLocal<String> threadLocal=new ThreadLocal<>();
    public SkuInfo getSkuInfoFromRedisWithThreadLocal(Long skuId) {
        //sku:24::info
        String cacheKey= RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        //a.从缓存中查询数据
        SkuInfo skuInfoRedis = (SkuInfo)redisTemplate.opsForValue().get(cacheKey);
        //b.如果缓存中没有数据查询数据库
        if(skuInfoRedis==null){
            //还有很多操作要执行 去缓存 查询逻辑等 100行代码要执行
            boolean accquireLock=false;
            String token=threadLocal.get();
            //定义一个锁的名称 减小锁的粒度
            String lockKey="lock-"+skuId;
            //第一次来 还没有参与自旋
            if(token==null){
                token = UUID.randomUUID().toString();
                accquireLock = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 30, TimeUnit.MINUTES);
            }else{
                //代表自旋拿到锁
                accquireLock=true;
            }
            if (accquireLock) {
                SkuInfo skuInfoDb = doBusiness(skuId, cacheKey);
                String luaScript="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(luaScript);
                redisScript.setResultType(Long.class);
                redisTemplate.execute(redisScript, Arrays.asList(lockKey),token);
                threadLocal.remove();
                return skuInfoDb;
            } else {
                //自旋 只做一件事情 拿锁
                for(;;){
                    SleepUtils.sleepMillis(50);
                    //重试去拿锁
                    boolean retryAccquireLock = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 30, TimeUnit.MINUTES);
                    if (retryAccquireLock) {
                        //拿到锁以后就不需要自旋了,把拿到锁的情况标记一下
                        threadLocal.set(token);
                        break;
                    }
                }
               return getSkuInfoFromRedisWithThreadLocal(skuId);
            }
        }else{
            return skuInfoRedis;
        }
    }

    private SkuInfo doBusiness(Long skuId, String cacheKey) {
        SkuInfo skuInfoDb = getSkuInfoFromDB(skuId);
        //c.把数据放入缓存
        redisTemplate.opsForValue().set(cacheKey, skuInfoDb, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
        return skuInfoDb;
    }

    //从redis中查询商品基本信息
    private SkuInfo getSkuInfoFromRedis2(Long skuId) {
        //sku:24::info
        String cacheKey= RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        //a.从缓存中查询数据
        SkuInfo skuInfoRedis = (SkuInfo)redisTemplate.opsForValue().get(cacheKey);
        //b.如果缓存中没有数据查询数据库
        if(skuInfoRedis==null){
            //有可能造成缓存穿透
            boolean flag = skuBloomFilter.contains(skuId);
            SkuInfo skuInfoDb=null;
            if(flag){
                skuInfoDb = getSkuInfoFromDB(skuId);
            }
            //c.把数据放入缓存
            redisTemplate.opsForValue().set(cacheKey,skuInfoDb,RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
            return skuInfoDb;
        }
        return skuInfoRedis;
    }

    //从redis中查询商品基本信息
    private SkuInfo getSkuInfoFromRedis1(Long skuId) {
        //sku:24::info
        String cacheKey= RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        //a.从缓存中查询数据
        SkuInfo skuInfoRedis = (SkuInfo)redisTemplate.opsForValue().get(cacheKey);
        //b.如果缓存中没有数据查询数据库
        if(skuInfoRedis==null){
            //有可能造成缓存穿透
            SkuInfo skuInfoDb = getSkuInfoFromDB(skuId);
            //判断从数据库中查询出来的值是否为空
            if(skuInfoDb==null){
                SkuInfo emptySkuInfo = new SkuInfo();
                redisTemplate.opsForValue().set(cacheKey,emptySkuInfo,RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
            }
            //c.把数据放入缓存
            redisTemplate.opsForValue().set(cacheKey,skuInfoDb,RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
            return skuInfoDb;
        }
        return skuInfoRedis;
    }

    //从数据库中查询商品基本信息
    private SkuInfo getSkuInfoFromDB(Long skuId) {
        //1.查询商品的基本信息
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        //2.查询商品的图片信息
        if(skuInfo!=null){
            QueryWrapper<SkuImage> wrapper = new QueryWrapper<>();
            wrapper.eq("sku_id",skuId);
            List<SkuImage> skuImageList = skuImageService.list(wrapper);
            skuInfo.setSkuImageList(skuImageList);
        }
        return skuInfo;
    }

    @Override
    public Map<Object, Object> getSalePropertyAndSkuIdMapping(Long productId) {
        Map<Object, Object> salePropertyAndSkuIdMap=new HashMap<>();
        List<Map> retListMap=skuSalePropertyValueService.getSalePropertyAndSkuIdMapping(productId);
        for (Map retMap : retListMap) {
            salePropertyAndSkuIdMap.put(retMap.get("sale_property_value_id"),retMap.get("sku_id"));
        }
        return salePropertyAndSkuIdMap;
    }

    @Override
    public List<ProductSalePropertyKey> getSpuSalePropertyAndSelected(Long productId, Long skuId) {
        return salePropertyKeyMapper.getSpuSalePropertyAndSelected(productId,skuId);
    }
}
